package com.cch.cooperation.biz.common.lock;

import com.cch.cooperation.common.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁代理
 *
 * @author cch on 2020/11/09
 **/
@Slf4j
@Component
public class LockerProxy {

    @Resource
    private RedissonClient redissonClient;

    public interface CallBack {
        void invoke() throws Exception;
    }

    public interface CallBackWithReturn<T> {
        T invoke() throws Exception;
    }

    public <T> T lock(CallBackWithReturn<T> cb, String lockKey, long timeout, long waitTimeout, TimeUnit unit) {
        RLock rLock = redissonClient.getLock(lockKey);
        try {
            if (!rLock.tryLock(waitTimeout, timeout, unit)) {
                throw new BizException("当前系统正忙，请稍后再试");
            }
        } catch (InterruptedException e) {
            log.error("[LOCK-PROXY]锁等待被中断", e);
            throw new BizException("当前系统正忙，请稍后再试");
        }
        try {
            return cb.invoke();
        } catch (BizException e) {
            throw e;
        } catch (Exception e) {
            log.error("[LOCK-PROXY]执行异常", e);
            throw new RuntimeException(e);
        } finally {
            Runnable unlockCall = () -> {
                if (!rLock.isHeldByCurrentThread()) {
                    log.warn("[LOCK-PROXY]锁超时警告，lock key:{},cb:{}的锁方法执行时间超过锁超时，请调整锁超时时间！！", lockKey, cb.getClass().getName());
                } else {
                    rLock.unlockAsync();
                }
            };
            invokeUnlock(unlockCall);
        }
    }

    public void lock(CallBack cb, String lockKey) throws BizException {
        lock(generateCallBackWithReturn(cb), lockKey, 5, 10, TimeUnit.SECONDS);
    }

    public <T> T lock(CallBackWithReturn<T> cb, String lockKey) throws BizException {
        return lock(cb, lockKey, 5, 10, TimeUnit.SECONDS);
    }

    public boolean isLocked(String lockKey) {
        RLock rLock = redissonClient.getLock(lockKey);
        return rLock.isLocked();
    }

    private void invokeUnlock(Runnable runnable) {
        if (!TransactionSynchronizationManager.isActualTransactionActive()) {
            runnable.run();
        } else {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    runnable.run();
                }
            });
        }
    }

    private CallBackWithReturn<Void> generateCallBackWithReturn(CallBack cb) {
        return () -> {
            cb.invoke();
            return null;
        };
    }
}
